﻿(*
    F# version of the QuantLib Bond Example
*)

#r "../Debug/Cephei.Kernel.dll"
#r "../Debug/Cephei.Core.dll"
#r "../Debug/Cephei.QL121.dll"
#r "../Debug/Cephei.Fun121.dll"

open System
open Cephei

let calendar            = Fun.Times.Calendars.TARGET.Create ()
let settlementDate      = DateTime (2008, 9, 18)
let fixingDays          = 3u
let settlementDays      = 3u
let todaysDate          = calendar.Advance (settlementDate, -(int fixingDays), QL.Times.TimeUnitEnum.Days, None, None)
let session             = Fun.Sessions.Create todaysDate

printf "Today: %A, %A\n" todaysDate.DayOfWeek todaysDate
printf "Settlement date: %A, %A\n" settlementDate.DayOfWeek settlementDate

(*
    Rate helpers
*)
let zc3mQuote           = 0.0096
let zc6mQuote           = 0.0145
let zc1yQuote           = 0.0194

let zc3mRate            = Fun.Quotes.SimpleQuote.Create (Some zc3mQuote)
let zc6mRate            = Fun.Quotes.SimpleQuote.Create (Some zc6mQuote)
let zc1yRate            = Fun.Quotes.SimpleQuote.Create (Some zc1yQuote)

let zcBondsDayCounter   = Fun.Times.Daycounters.Actual365Fixed.Create () 

let months m            = Fun.Times.Period.Create (m, QL.Times.TimeUnitEnum.Months)
let ModifiedFollowing   = QL.Times.BusinessDayConventionEnum.ModifiedFollowing
let zc3m                = Fun.Termstructures.Yield.DepositRateHelper.Create (zc3mRate, (months 3), fixingDays, calendar, ModifiedFollowing, true, zcBondsDayCounter)
let zc6m                = Fun.Termstructures.Yield.DepositRateHelper.Create (zc6mRate, (months 6), fixingDays, calendar, ModifiedFollowing, true, zcBondsDayCounter)
let zc1y                = Fun.Termstructures.Yield.DepositRateHelper.Create (zc1yRate, (months 12), fixingDays, calendar, ModifiedFollowing, true, zcBondsDayCounter)

let bondInstruments     = Fun.Termstructures.Yield.RateHelper.CreateVector ([zc3m;zc6m;zc1y])
let termStrucDayCounter = Fun.Times.Daycounters.ActualActual.Create (Some QL.Times.Daycounters.ActualActual.ConventionEnum.ISDA)
let tolerance           = 1.0e-15
let bondTermStructure   = Fun.Termstructures.Yield.PiecewiseYieldCurveDiscountLogLinear.Create (settlementDate, bondInstruments, termStrucDayCounter, tolerance)
    
(*
    Bond Data
*)
let redemption          = 100.0

let issueDates          = [ DateTime (2005, 3, 15)
                          ; DateTime (2005, 6, 15)
                          ; DateTime (2006, 6, 30)
                          ; DateTime (2002, 11, 15)
                          ; DateTime (1987, 5, 15)
                          ]

let maturities          = [ DateTime (2010, 8, 31)
                          ; DateTime (2011, 8, 31)
                          ; DateTime (2013, 8, 31)
                          ; DateTime (2018, 8, 31)
                          ; DateTime (2038, 5, 15)
                          ]
 
let couponRates         = [ 0.02375
                          ; 0.04625
                          ; 0.03125
                          ; 0.04000
                          ; 0.04500
                          ]

let marketQuotes        = [ 100.390625
                          ; 106.21875
                          ; 100.59375
                          ; 101.6875
                          ; 102.140625
                          ]

let two2one a b         = List.map2 (fun e y -> (e,y)) a b
let combine             = two2one (two2one issueDates maturities) (two2one couponRates marketQuotes)

let quote               = List.map (fun q -> Fun.Quotes.SimpleQuote.Create (Some q)) marketQuotes 
let quoteRate r         = Fun.Quotes.SimpleQuote.Create (Some r)
let usCalendar          = Fun.Times.Calendars.UnitedStates.Create (Some QL.Times.Calendars.UnitedStates.MarketEnum.GovernmentBond)
let unadjusted          = QL.Times.BusinessDayConventionEnum.Unadjusted 
let backward            = QL.Times.DateGeneration.RuleEnum.Backward
let semiannual          = Fun.Times.Period.Create (QL.Times.FrequencyEnum.Semiannual)
let schedule i m        = Fun.Times.Schedule.Create (i, m, semiannual, usCalendar, unadjusted, unadjusted, backward, false, None, None)
let coupons q           = Fun.Doubles.CreateVector ([q])
let actualActualBond    = Fun.Times.Daycounters.ActualActual.Create (Some QL.Times.Daycounters.ActualActual.ConventionEnum.Bond)
let fixedHelper q s c i = Fun.Termstructures.Yield.FixedRateBondHelper.Create (q, settlementDays, redemption, s, c, actualActualBond, Some unadjusted, Some redemption, Some i)
let schedules           = List.map2 schedule issueDates maturities
let rateHelpers         = List.map (fun ((i,m),(c,q))-> fixedHelper (quoteRate q) (schedule i m) (coupons c) i) combine

(*
    curve building
*)
let liborRates          = [ (1, 'w', 0.043375)
                          ; (1, 'm', 0.031875)
                          ; (3, 'm', 0.0320375)
                          ; (6, 'm', 0.03385)
                          ; (9, 'm', 0.0338125)
                          ; (1, 'y', 0.031875)
                          ]

// Building of the Libor forecasting curve
// deposits
let d1wQuote            = 0.043375;
let d1mQuote            = 0.031875;
let d3mQuote            = 0.0320375;
let d6mQuote            = 0.03385;
let d9mQuote            = 0.0338125;
let d1yQuote            = 0.0335125;
// swaps
let s2yQuote            = 0.0295;
let s3yQuote            = 0.0323;
let s5yQuote            = 0.0359;
let s10yQuote           = 0.0412;
let s15yQuote           = 0.0433;

// SimpleQuote stores a value which can be manually changed;
// other Quote subclasses could read the value from a database
// or some kind of data feed.

// deposits

let d1wRate             = Fun.Quotes.SimpleQuote.Create (Some d1wQuote)
let d1mRate             = Fun.Quotes.SimpleQuote.Create (Some d1mQuote)
let d3mRate             = Fun.Quotes.SimpleQuote.Create (Some d3mQuote)
let d6mRate             = Fun.Quotes.SimpleQuote.Create (Some d6mQuote)
let d9mRate             = Fun.Quotes.SimpleQuote.Create (Some d9mQuote)
let d1yRate             = Fun.Quotes.SimpleQuote.Create (Some d1yQuote)
// swaps
let s2yRate             = Fun.Quotes.SimpleQuote.Create (Some s2yQuote)
let s3yRate             = Fun.Quotes.SimpleQuote.Create (Some s3yQuote)
let s5yRate             = Fun.Quotes.SimpleQuote.Create (Some s5yQuote)
let s10yRate            = Fun.Quotes.SimpleQuote.Create (Some s10yQuote)
let s15yRate            = Fun.Quotes.SimpleQuote.Create (Some s15yQuote)

let depositDayCounter   = Fun.Times.Daycounters.Actual360.Create ()

let period n t          = match t with
                          | 'w' -> Fun.Times.Period.Create (n, QL.Times.TimeUnitEnum.Weeks)
                          | 'm' -> Fun.Times.Period.Create (n, QL.Times.TimeUnitEnum.Months)
                          | 'y' -> Fun.Times.Period.Create (n, QL.Times.TimeUnitEnum.Years) 
                          | 'd' -> Fun.Times.Period.Create (n, QL.Times.TimeUnitEnum.Days) 
                          | _   -> raise (new Exception ("invalid period type"))

let d1w                 = Fun.Termstructures.Yield.DepositRateHelper.Create (d1wRate, (period 1 'w'), fixingDays, calendar, ModifiedFollowing, true, depositDayCounter)
let d1m                 = Fun.Termstructures.Yield.DepositRateHelper.Create (d1mRate, (period 1 'm'), fixingDays, calendar, ModifiedFollowing, true, depositDayCounter)
let d3m                 = Fun.Termstructures.Yield.DepositRateHelper.Create (d3mRate, (period 3 'm'), fixingDays, calendar, ModifiedFollowing, true, depositDayCounter)
let d6m                 = Fun.Termstructures.Yield.DepositRateHelper.Create (d1wRate, (period 6 'm'), fixingDays, calendar, ModifiedFollowing, true, depositDayCounter)
let d9m                 = Fun.Termstructures.Yield.DepositRateHelper.Create (d9mRate, (period 9 'm'), fixingDays, calendar, ModifiedFollowing, true, depositDayCounter)
let d1y                 = Fun.Termstructures.Yield.DepositRateHelper.Create (d1yRate, (period 1 'y'), fixingDays, calendar, ModifiedFollowing, true, depositDayCounter)

// setup swaps
let annual               = QL.Times.FrequencyEnum.Annual     
let thirty360European   = Fun.Times.Daycounters.Thirty360.Create (Some QL.Times.Daycounters.Thirty360.ConventionEnum.European)
let forwardStart        = period 1 'd'
let swFloatingLegIndex  = Fun.Indexes.Ibor.Euribor.Create (period 6 'm')
let s2y                 = Fun.Termstructures.Yield.SwapRateHelper.Create (s2yRate, (period 2 'y'), calendar, annual, unadjusted, thirty360European, swFloatingLegIndex, None, Some forwardStart, None)
let s3y                 = Fun.Termstructures.Yield.SwapRateHelper.Create (s3yRate, (period 3 'y'), calendar, annual, unadjusted, thirty360European, swFloatingLegIndex, None, Some forwardStart, None)
let s5y                 = Fun.Termstructures.Yield.SwapRateHelper.Create (s5yRate, (period 5 'y'), calendar, annual, unadjusted, thirty360European, swFloatingLegIndex, None, Some forwardStart, None)
let s10y                = Fun.Termstructures.Yield.SwapRateHelper.Create (s10yRate, (period 10 'y'), calendar, annual, unadjusted, thirty360European, swFloatingLegIndex, None, Some forwardStart, None)
let s15y                = Fun.Termstructures.Yield.SwapRateHelper.Create (s15yRate, (period 15 'y'), calendar, annual, unadjusted, thirty360European, swFloatingLegIndex, None, Some forwardStart, None)

(*
    Curve building
*)
let depoSwapInstruments = Fun.Termstructures.Yield.RateHelper.CreateVector ([d1w;d1m;d3m;d6m;d9m;d1y;s2y;s3y;s5y;s10y;s15y])
let depoSwapTermStructure = Fun.Termstructures.Yield.PiecewiseYieldCurveDiscountLogLinear.Create (settlementDate, depoSwapInstruments, termStrucDayCounter, tolerance)

(*
    Bonds to be priced
*)
let faceAmount          = 100.0
let bondEngine          = Fun.Pricingengines.Bond.DiscountingBondEngine.Create(Some (bondTermStructure :> QL.Termstructures.IYieldTermStructure), None) 
let following           = QL.Times.BusinessDayConventionEnum.Following 

// Zero coupon bond

let zeroCouponBond      = Fun.Instruments.Bonds.ZeroCouponBond.Create (settlementDays, usCalendar, faceAmount, DateTime(2013, 8,15), Some following, Some 116.92, Some (DateTime(2003,8,15)), bondEngine)

// Fixed 4.5% US Treasury Note

let fixedBondSchedule   = Fun.Times.Schedule.Create (DateTime(2007,05,15), DateTime(2017,5,15), semiannual, usCalendar, unadjusted, unadjusted, QL.Times.DateGeneration.RuleEnum.Backward, false, None, None)
let fixedRateBond       = Fun.Instruments.Bonds.FixedRateBond.Create (settlementDays, faceAmount, fixedBondSchedule,(coupons 0.045), (actualActualBond :> QL.Times.IDayCounter), Some ModifiedFollowing, Some 100.0, Some (DateTime(2007,5, 15)), None, bondEngine)

// Floating rate bond (3M USD Libor + 0.1%)
// Should and will be priced on another curve later...

let libor3m             = Fun.Indexes.Ibor.USDLibor.Create ((period 3 'm'), Some (depoSwapTermStructure :> QL.Termstructures.IYieldTermStructure))
let quarterly           = Fun.Times.Period.Create (QL.Times.FrequencyEnum.Quarterly)
let usNYSE              = Fun.Times.Calendars.UnitedStates.Create (Some QL.Times.Calendars.UnitedStates.MarketEnum.NYSE)
let floatingBondSchedule = Fun.Times.Schedule.Create (DateTime(2005,10,21), DateTime(2010,10,21), quarterly, usNYSE, unadjusted, unadjusted, QL.Times.DateGeneration.RuleEnum.Backward, true, None, None)
let floatingRateBond    = Fun.Instruments.Bonds.FloatingRateBond.Create (settlementDays, faceAmount, floatingBondSchedule, libor3m, depositDayCounter, Some ModifiedFollowing, Some 2u, Some (coupons 1.0), Some (coupons 0.001), None, None, Some true, Some faceAmount, Some (DateTime(2005, 10, 21)), bondEngine)

// Coupon pricers

let pricer              = Fun.Cashflows.BlackIborCouponPricer.Create (None)
let volatility          = 0.0
let Actual365Fixed      = Fun.Times.Daycounters.Actual365Fixed.Create ();
let vol                 = Fun.Termstructures.Volatility.Optionlet.ConstantOptionletVolatility.Create (settlementDays, calendar, ModifiedFollowing,volatility, Actual365Fixed)
//use fluent interface to set capvol
let pricer2             = pricer.SetCapletVolatility (Some (vol :> QL.Termstructures.Volatility.Optionlet.IOptionletVolatilityStructure))

(*
    Bond Pricing
*)
let Y (b : QL.Instruments.IBond) = b.Yield (depositDayCounter,QL.CompoundingEnum.Compounded, QL.Times.FrequencyEnum.Annual, None, None)

let s = Fun.Sessions.Create (DateTime.Today)

printf "                  %10s %10s %10s\n" "ZC" "Fixed" "Floating"
printf "Net present value %10f %10f %10f\n" zeroCouponBond.NPV fixedRateBond.NPV floatingRateBond.NPV 
printf "Clean Price       %10f %10f %10f\n" (zeroCouponBond.CleanPrice()) (fixedRateBond.CleanPrice()) (floatingRateBond.CleanPrice())
printf "Dirty Price       %10f %10f %10f\n" (zeroCouponBond.DirtyPrice()) (fixedRateBond.DirtyPrice()) (floatingRateBond.DirtyPrice())
printf "Accrued coupon    %10f %10f %10f\n" (zeroCouponBond.AccruedAmount(None)) (fixedRateBond.AccruedAmount(None)) (floatingRateBond.AccruedAmount(None))
printf "Previous coupon   %10s %10f %10f\n" "N/A"(fixedRateBond.PreviousCouponRate (None)) (floatingRateBond.PreviousCouponRate (None))
printf "next coupon       %10s %10f %10f\n" "N/A"(fixedRateBond.NextCouponRate (None)) (floatingRateBond.NextCouponRate(None))
printf "Yield             %10f %10f %10f\n" (Y zeroCouponBond) (Y fixedRateBond) (Y floatingRateBond)

